package com.aop.SpElAop.ExpressionParser;

/**
 * @Package: com.aop.SpElAop
 * @ClassName: LogExpressionEvaluator
 * @Author: wulongkai
 * @CreateTime: 2024/7/9 16:35
 * @Description:
 */

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 解析Log中的SpEL表达式
 */
public class CommonExpressionEvaluator extends AbstractCachedExpressionEvaluator {

    private final Map<ExpressionKey, Expression> messageCache = new ConcurrentHashMap<>(64);

    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);

    /**
     * 为指定方法的指定事件处理创建合适的 {@link EvaluationContext}。
     */
    public EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Class<?> targetClass,
                                                     @Nullable Object result, @Nullable String errorMsg, @Nullable BeanFactory beanFactory) {
        ExpressionRootObject rootObject = new ExpressionRootObject(method, args, target, targetClass);
        CommonEvaluationContext commonEvaluationContext = new CommonEvaluationContext(
                rootObject, getTargetMethod(targetClass, method), args, getParameterNameDiscoverer(), result, errorMsg
        );
        if (beanFactory != null) {
            commonEvaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }
        return commonEvaluationContext;
    }

    /**
     * 获取真正的具体的方法并缓存
     *
     * @param targetClass 目标class
     * @param method      来自接口或者父类的方法签名
     * @return 目标class实现的具体方法
     */
    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if (targetMethod == null) {
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }


    /**
     * 解析指定表达式。
     */
    public <T> T parse(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext, Class<T> tClass) {
        return getExpression(this.messageCache, methodKey, expression).getValue(evalContext, tClass);
    }

    void clear() {
        this.messageCache.clear();
    }
}


